package com.lrd.snapshotorder.util;

public class SnowFlakeIdGenerator {

    public static final SnowFlakeIdGenerator INIT = new SnowFlakeIdGenerator(2, 3);

    /**
     * 起始的时间戳
     */
    private final static long START_STMP = 1480166465631L;

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
    private final static long MACHINE_BIT = 5;   //机器标识占用的位数
    private final static long DATACENTER_BIT = 5;//数据中心占用的位数


    /**
     * 每一部分的最大值
     */
    /**
     * 数据中心数量 -- 用位运算计算出最大支持的数据中心数量: 31
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    /**
     * 机器数量 -- 用位运算计算出最大支持的机器数量: 31
     */
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    /**
     * 序列号数量 -- 用位运算计算出 12 位能存储的最大正整数: 4095
     */
    /*-1L == 1111 1111
    -1L << 12 也就是: 1111 1111 0000 0000 0000 0000

    -1L 异或运算
    0^0 和 1^1=0
    1^0 和 0^1=1

    1111 1111 0000 0000 0000 0000
    A
    1111 1111 1111 1111 1111 1111
    ------------------------------------------------------
    0000 0000 1111 1111 1111 1111 换算成十进制:4095*/
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);


    /**
     * 每一部分向左的位移
     */
    /**
     * 机器标识较序列号的偏移量  12位
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    /**
     * 数据中心较机器标识的偏移量  17位
     */
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    /**
     * 时间戳较数据中心的偏移量
     */
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private long datacenterId;  //数据中心
    private long machineId;     //机器标识
    private long sequence = 0L; //序列号
    private long lastStmp = -1L;//上一次时间戳


   /* public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(2, 3);

        //  << ：位移运算服， <<左移运算，>>右移运算，还有不带符号的位移运算 >>>
        // 1 << 12：首先把1转为二进制数字 0000 0000 0000 0000 0000 0000 0000 0001
        //          然后将上面的二进制数字向左移动 12 位后面补 0 得到 0010 0000 0000 0000 0000 0000 0000 0000
        //          最后将得到的二进制数字转回对应类型的十进制
        System.out.println(1 << 12);
        for (int i = 0; i < (1 << 12); i++) {
            System.out.println(snowFlake.nextId());
        }

    }*/

    /**
     * @param datacenterId
     * @param machineId
     */
    public SnowFlakeIdGenerator(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0（数据中心 ID 不能大于 MAX_DATACENTER_NUM 或小于 0）");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0（机器 ID 不能大于 MAX_MACHINE_NUM 或小于 0）");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     * 产生下一个ID
     */
    public synchronized long nextId() {
        // 获取当前的时间戳
        long currStmp = getNewstmp();

        // 如果当前时间戳小于上次时间戳，则抛出异常
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id（时钟回拨。拒绝生成 id）");
        }

        // 如果当前时间戳等于上次的时间戳，说明是在同一毫秒内调用的雪花算法
        if (currStmp == lastStmp) {
            //相同毫秒内，序列号自增
            /*  & 算法：1 & 1 =1，其余况都返回 0 （相同为1，其他为0）
            4095:0000 0000 1111 1111 1111 1111
            &
            4096:0000 0001 0000 0000 0000 0000
            ------------------------------------
            等于:0000 0000 0000 0000 0000 0000
            */
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大 4095
            if (sequence == 0L) {
                // 获取下一毫秒的时间戳给到当前时间戳
                currStmp = getNextMill();
            }
        } else {
            //不同毫秒内，序列号置为0
            sequence = 0L;
        }

        // 当前时间戳存档记录，用于下次产生 id 时对比，是否为相同时间戳
        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT // 时间戳部分   22
                | datacenterId << DATACENTER_LEFT       // 数据中心部分 17
                | machineId << MACHINE_LEFT             // 机器标识部分 12
                | sequence;                             // 序列号部分
    }

    /**
     * @return 获取当前时间戳
     */
    private long getNewstmp() {
        return System.currentTimeMillis();
    }


    /**
     * @return 获取下一时间的时间戳
     */
    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }


}
